package br.edu.ufcg.splab.util;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import br.edu.ufcg.splab.core.InterfaceEdge;
import br.edu.ufcg.splab.core.InterfaceGraph;
import br.edu.ufcg.splab.core.InterfaceVertex;

public class ModelPropertyExtractor {

	private List<InterfaceEdge> branches;
	private List<InterfaceEdge> loops;
	private List<InterfaceEdge> joins;
	private List<InterfaceEdge> flows;
	private List<InterfaceVertex> longestPath;
	
	public int[] getPatterns(InterfaceGraph lts){
		int[] patterns = new int[4];
		branches = new ArrayList<InterfaceEdge>();
		loops = new ArrayList<InterfaceEdge>();
		joins = new ArrayList<InterfaceEdge>();
		flows = new ArrayList<InterfaceEdge>();
		longestPath= new ArrayList<InterfaceVertex>();
		
		List<InterfaceVertex> visitedStates = new ArrayList<InterfaceVertex>();
		visitedStates.add(lts.getRoot());
		dfsGetLoops(lts.getRoot(), visitedStates);
		//System.out.println(loops);
		
		//check if there is a problem with analysis of joins on roots (or even if that is possible)
		visitedStates = new ArrayList<InterfaceVertex>();
		visitedStates.add(lts.getRoot());
		traverseGraph(lts.getRoot(),visitedStates);
		/*System.out.println(joins);
		System.out.println(branches);
		System.out.println(flows);
		System.out.print("data: ");*/
		//System.out.println("Size: "+longestPath.size()+" Branches: "+(branches.size()/2)+" Joins: "+(joins.size()/2)+" Loops: "+loops.size());
		patterns[0] = longestPath.size();
		patterns[1] = branches.size()/2;
		patterns[2] = joins.size()/2;
		patterns[3] = loops.size();
		
		System.out.println(Arrays.toString(patterns));
		
		return patterns;
	}

	
	private void dfsGetLoops(InterfaceVertex vertex, List<InterfaceVertex> visitedStates){
		if(!vertex.isLeaf()) {
			for (InterfaceEdge transition : vertex.getOutTransitions()) {
				InterfaceVertex vertexTo = transition.getTo();
				if(vertex.getLabel().equals(vertexTo.getLabel()) || alreadyVisited(visitedStates, vertexTo)) {
					//Self Loop OR Reach a repeated state
					addPattern(this.loops, transition);
				} else {
					visitedStates.add(vertexTo);					
					dfsGetLoops(vertexTo, visitedStates);
					visitedStates.remove(vertexTo);
				}
			}
		}else{
			if(visitedStates.size() > longestPath.size()){
				longestPath = new ArrayList<InterfaceVertex>();
				longestPath.addAll(visitedStates);
			}
		}
	}

	private void traverseGraph(InterfaceVertex currentState, List<InterfaceVertex> visitedStates){
		
		if(!currentState.isLeaf()) {
			
			int totalJL = 0;
			List<InterfaceEdge> possibleBranches = new ArrayList<InterfaceEdge>();
			for (InterfaceEdge transition : currentState.getOutTransitions()) {
				InterfaceVertex vertexTo = transition.getTo();
				
				if(!loops.contains(transition) && vertexTo.getInTransitions().size() > 1){
					//avoids analysing loop edges as joins!
					verifyJoin(transition);
				}
				
				if(!currentState.getLabel().equals(vertexTo.getLabel()) && !alreadyVisited(visitedStates, vertexTo)) {
					//Stop if you reach a self Loop OR a repeated state
					visitedStates.add(vertexTo);				
					traverseGraph(vertexTo, visitedStates);
					visitedStates.remove(vertexTo);
				}
				
				if(!this.loops.contains(transition)  && !this.joins.contains(transition)){
					possibleBranches.add(transition);
				}else{
					totalJL = totalJL + 1;
				}
			}
			
			//after checking all outgoing transitions, check the ones that are joins/loops. The remaining will either be a normal or a branch.
			//We use the following: totalSons - Joins - Loops = x. If x > 1 then all remaining children are branching transitions, otherwise
			// the remaining transitions will be a main sequence transition (e.g. a join output or a main sequence transitions). 
			//IMPORTANT: This may be innaccurate depending on the input model. However, the tie break becomes the theory: "states with more than
			// one outgoing transition are branching states". This complexity is required just to avoid classifying joins as branches inappropriately.
			if(currentState.getOutTransitions().size() - totalJL < 2){
				addAll(this.flows, possibleBranches);
			}else{
				addAll(this.branches, possibleBranches);
			}
		}
	}

	private void addAll(List<InterfaceEdge> list1, List<InterfaceEdge> list2) {
		for(InterfaceEdge transition : list2){
			if(!list1.contains(transition)){
				list1.add(transition);
			}
		}
	}


	private void verifyJoin(InterfaceEdge transition) {
		// TODO Auto-generated method stub
		InterfaceVertex state = transition.getTo();
		List<InterfaceEdge> possibleJoins = new ArrayList<InterfaceEdge>();
		possibleJoins.addAll(state.getInTransitions());
		
		possibleJoins.remove(transition);
		for(InterfaceEdge incoming : state.getInTransitions()){
			if(loops.contains(incoming) || joins.contains(incoming)){
				possibleJoins.remove(incoming);
			}
		}
		if(possibleJoins.size() > 0){
			for(InterfaceEdge join : possibleJoins){
				addPattern(this.joins, join);
			}
			addPattern(this.joins,transition);
		}
		
	}

	//Method created to avoid repeated insertions on each pattern's collection.
	private void addPattern(List<InterfaceEdge> patterns, InterfaceEdge transition) {		
		if(!patterns.contains(transition)){
			patterns.add(transition);
		}
	}


	private boolean alreadyVisited(List<InterfaceVertex> visitedStates, InterfaceVertex state) {		
		return visitedStates.contains(state);
	}

	
	public static void main(String[] args) {
		try {
			String folder = "test-data/model_generator/property_extraction/";
			//String[] filenames = {"well_formed1","general1","Baseline_1","Baseline_2","Baseline_3","Baseline_4","Baseline_5"};
			ModelPropertyExtractor mp = new ModelPropertyExtractor();
			
			
			List<String> filenames = getFileListTGF("test-data/model_generator/CB/");
			double[] avgPatterns = new double[4];
			for (int i = 0; i < avgPatterns.length; i++) {
				avgPatterns[i] = 0; //initiate the array with zero values.
			}
			
			for (int i = 0; i < filenames.size(); i++) {
				ReadTGF parser = new ReadTGF();
				InterfaceGraph lts = parser.getGraph(filenames.get(i));
				int[] currentPatterns = mp.getPatterns(lts);
				for (int j = 0; j < avgPatterns.length; j++) {
					avgPatterns[j] = avgPatterns[j]+currentPatterns[j];
				}
			}
			
			System.out.println("---------");
			for (int i = 0; i < avgPatterns.length; i++) {				
				avgPatterns[i] = Math.ceil(avgPatterns[i]/Double.valueOf(filenames.size())); //get the average value for patterns extracted from industry models.
				System.out.print((int)(avgPatterns[i])+" ");
			}
			
			System.out.println();
			
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			
		}
	}
	
	
	
	private static List<String> getFileListTGF(String path_graphs) {
		List<String> fileNames = new ArrayList<String>();
		String aux = "UC_C";
		String format = "_filtered.tgf";
		//CB		
		for (int i = 1; i <= 6; i++) {
			fileNames.add(path_graphs+aux+"B0"+i+format);
		}
		
		//CC
		for (int i = 1; i <= 9; i++) {			
			fileNames.add(path_graphs+aux+"C0"+i+format);
		}
		fileNames.add(path_graphs+aux+"C10"+format);
		
		return fileNames;
	}
}

